/*
 * mysched.c -- implementation of thread scheduler
 *
 */

#include <mythread.h>
#include <malloc.h>
#include <stdio.h>
#include <signal.h>
#include <sys/time.h>

//#define DEBUG
//#define DEBUG2
//#define DEBUG3
//#define SOLARIS

static volatile int mythread_reschedule = FALSE;

static int mythread_scheduler(void)
{
    mythread_t th, current = mythread_self();
    int suspend = FALSE;
    mythread_queue_t q, qnext;

    while (mythread_reschedule) {
        mythread_reschedule = FALSE;

        if (*(mythread_readyq()) == NULL) {
            break; /* nobody to timeslice to -> done */
        }

        for (q = *(mythread_runq()); q != NULL; q = qnext) {
            qnext = q->next; /* q->next may be modified from here on */
            th = (mythread_t) q->item;

            if (th->reschedule) {
                th->reschedule = FALSE;

                if (th == current && th->state & MYTHREAD_RUN) {
                    suspend = TRUE;
                } else if (th->state & MYTHREAD_RUN && *(mythread_readyq()) != NULL) {
                    mythread_block_thread(th, mythread_readyq(), NULL);
                }
            }

            if (qnext == *(mythread_runq()) || qnext == q) {
                break;
            }
        }
    }

    return suspend;
}

/*
 * mythread_leave_kernel - leave the monolithic threading kernel
 */
void mythread_leave_kernel(void)
{
    mythread_t current = mythread_self();
    int block;

#ifdef DEBUG3
    if (mythread_kernel.count != 0) {
        printf("FATAL ERROR: leave kernel\n"); fflush(stdout);
    }
#endif

    mythread_leave_kernel_nonpreemptive(); /* leave kernel */ 

    while (mythread_reschedule && mythread_tryenter_kernel()) { /* reenter */
        if (mythread_scheduler() && *(mythread_readyq()) != NULL) {
	    current->state &= ~MYTHREAD_RUN;
	    mythread_block(mythread_readyq(), NULL);
        } else {
            mythread_leave_kernel_nonpreemptive(); /* leave kernel */ 
        }
    }
}

static void mythread_sighandler(int sig, siginfo_t *siginfo, void *ucp)
{
    mythread_t current = mythread_self();
    int suspend;

    current->reschedule = TRUE;
    mythread_reschedule = TRUE;

    if (!mythread_tryenter_kernel()) {
      return; /* someone else will reschedule me */
    }
        
    if (!current->reschedule) {
      mythread_leave_kernel_nonpreemptive(); /* leave kernel */ 
      return; /* someone else already rescheduled me */
    }

#ifdef DEBUG
    {
        extern sem_t mythread_kernel;
        printf("sighandler lwp %d count %d\n", current->lwpid, mythread_kernel.count);
        fflush(stdout);
    }
#endif
    do {
        if (mythread_scheduler() && *(mythread_readyq()) != NULL) {
            mythread_block(mythread_readyq(), NULL);
        } else {
            mythread_leave_kernel_nonpreemptive(); /* leave kernel */ 
        }
    } while (mythread_reschedule && mythread_tryenter_kernel()); /* reenter */
}

/*
 * mythread_init_sched - called once at the start of each thread
 * enable virtual timer signals, install signal handler for it and
 * set time slice to 20ms for virtual timer
 */
void mythread_init_sched(void)
{
    struct itimerval slice;
    sigset_t sigset;
    struct sigaction act;

    sigemptyset(&sigset);
    sigaddset(&sigset, SIGVTALRM);
    sigprocmask(SIG_UNBLOCK, &sigset, NULL);

    act.sa_handler = NULL;
    act.sa_sigaction = mythread_sighandler;
    sigemptyset(&act.sa_mask);
    act.sa_flags = SA_RESTART | SA_SIGINFO;
    sigaction(SIGVTALRM, &act, NULL);

    slice.it_value.tv_sec = 0;
    slice.it_value.tv_usec = 200;
    slice.it_interval.tv_sec = 0;
    slice.it_interval.tv_usec = 200;
    setitimer(ITIMER_VIRTUAL, &slice, NULL);
}

/*
 * mythread_exit_sched - called on thread terminiation (exit) by each thread
 * disable all the features activated in mythread_init_sched()
 */
void mythread_exit_sched(void)
{
    struct itimerval slice;
    sigset_t sigset;
    struct sigaction act;

    sigemptyset(&sigset);
    sigaddset(&sigset, SIGVTALRM);
    sigprocmask(SIG_BLOCK, &sigset, NULL);
#ifdef SOLARIS
    /*
     * handlers/signals are per LWP in Solaris, so we only need this there
     */
    act.sa_handler = SIG_DFL;
    act.sa_sigaction = mythread_sighandler;
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;
    sigaction(SIGVTALRM, &act, NULL);

    slice.it_value.tv_sec = 0;
    slice.it_value.tv_usec = 0;
    slice.it_interval.tv_sec = 0;
    slice.it_interval.tv_usec = 0;
    setitimer(ITIMER_VIRTUAL, &slice, NULL);
#endif
}

/* 
 * mythread_attr_setschedparam - set the value of scheduling
 * parameters ('sched_priority') in the attribute,
 * to define/change the thread priority, 
 * see /usr/include/linux/sched.h for struct sched_param
 */
int mythread_attr_setschedparam (mythread_attr_t *attr, 
				 struct sched_param *param)
{
  attr->param = *param;
}

/* 
 * mythread_attr_getschedparam - return the value of the schedling
 * parameters ('sched_priority') from the attribute
 */
int mythread_attr_getschedparam (const mythread_attr_t *attr, 
				 struct sched_param *param)
{
  *param = attr->param;
  return attr->param.sched_priority;
}
